{Get rid of a hash table and all its associated storage.}
function DisposeHashTable (theTable: HashTable): OSErr;
{Remove all the entries from the given table. Time is proportional to number of entries.}
function EmptyHashTable (theTable: HashTable): OSErr;
{Add a key/value pair to the table, or replace the existing value if the key is already present.}
{This is optimized for speed in the case where the old and new values are the same size.}
function SetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; valuePtr: Ptr; valueLength: Integer; var replaced: Boolean): OSErr;
{Given a key, return the offset and length of the associated data within the dataBlock.}
function GetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var found: Boolean): OSErr;
{Given a key, remove its entry. This can be expensive in terms of time.}
function RemoveHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var found: Boolean): OSErr;
{Return the number of entries in the table. This is a constant-time operation.}
function CountHashEntries (theTable: HashTable; var count: Longint): OSErr;
{Enumerate all the key/value pairs in the given hash table.}
{Start with state = 0. Caller must preserve state across calls.}
{When state becomes 0 after call, current results are to be ignored — there’s no more.}
{When state is nonzero after call, an entry is described by its offsets and lengths relative}
{to the start of the dataBlock. If you add or delete an entry when state <> 0, you’ll get}
{a paramErr result on the next call and state will be reset to zero.}
function GetNextHashEntry (theTable: HashTable; var keyOffset: Longint; var keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var state: Longint): OSErr;
{The efficiency of the table is defined as the number of chains divided by the number of}
{buckets. With no collisions, the efficiency is 1. As the length of chains increases, the}
{efficiency decreases below 1. By definition, an empty table has an efficiency of 1.}
{This is a constant-time operation.}
function HashEfficiency (theTable: HashTable; var efficiency: Real): OSErr;
{The occupancy of the table is defined as the number of chains divided by the number of}
{available slots. An empty table has an occupancy of 0. When all the slots have been filled,}
{occupancy becomes 1. Due to collisions, it’s possible to have more entries than slots}
{and still not have an occupancy of 1. This is a constant-time operation.}
function HashSlotOccupancy (theTable: HashTable; var occupancy: Real): OSErr;
{Return the number of slots allocated to this table at creation or rehash time.}
function HashTableSlotCount (theTable: HashTable; var numSlots: Integer): OSErr;
{Change the number of slots in the table. Time is proportional to number of entries.}
function ReHash (theTable: HashTable; numSlots: Integer): OSErr;
{Find out how much memory we would get back by calling CompactHashDataSpace.}
function HashRecoverableSpace (theTable: HashTable; var recoverableSpace: Size): OSErr;
{Remove gaps from the data area. Time is proportional to total size active keys and values.}
{This temporarily uses additional space equal to the active data, plus an expansion reserve.}
{This returns paramErr if called when HashRecoverableSpace is zero.}
function CompactHashSpace (theTable: HashTable): OSErr;
implementation
type
TableEntryOffset = Longint;
TableEntry = record
keyOff: Longint;
keyLen: Integer;
valLen: Integer; {No offset for value; it follows key.}
nextOffset: TableEntryOffset; {Relative to beginning of block.}
end;
TableEntryPtr = ^TableEntry;
Table = record
dataHandle: Handle;
dataLimit: Longint;
expandAmount: Size;
bucketsHandle: Handle;
freeBucketOffset: Longint;
maxBuckets: Longint;
maxIndex: Integer;
hashFunc: ProcPtr;
matchFunc: ProcPtr;
iteratorSlot: Integer;
iteratorBucket: TableEntryOffset;
iteratorCheckCount: Longint;
bucketCount: Longint;
usedSlotCount: Integer;
totalGapSpace: Size;
slots: array[0..0] of TableEntryOffset;
end;
TablePtr = ^Table;
TableHandle = ^TablePtr;
const
EndOfList = -1; {In-list offsets are >= 0.}
function DefaultHashFunction (keyData: Ptr; keyLength, modulus: Integer): Integer;
var
hashResult: Longint;
p: Ptr;
i: Integer;
begin
hashResult := 0;
p := keyData;
for i := 1 to keyLength do
begin
hashResult := BROTR(BXOR(hashResult, p^), 5);
p := Ptr(ORD(p) + SIZEOF(SignedByte));
end;
if hashResult < 0 then {We need a positive result.}
hashResult := BNOT(hashResult); {Rather than negation, because --MAXLONGINT = -MAXLONGINT.}
{Just like GetHashEntry, except hashKey and bucket are always returned.}
function InternalGetHashEntry (theTable: HashTable; dataBlock: Handle; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var hashKey: Integer; var bucket: TableEntryOffset): Boolean;
function GetHashEntry (theTable: HashTable; keyPtr: Ptr; keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var found: Boolean): OSErr;
var
hashKey: Integer;
bucket: TableEntryOffset;
begin
dataBlock := TableHandle(theTable)^^.dataHandle;
found := InternalGetHashEntry(theTable, dataBlock, keyPtr, keyLength, valueOffset, valueLength, hashKey, bucket);
GetHashEntry := noErr;
end; {GetHashEntry}
function CountHashEntries (theTable: HashTable; var count: Longint): OSErr;
iteratorBucket := EndOfList; {Start at the beginning.}
iteratorSlot := -1;
iteratorCheckCount := bucketCount;
end;
while (iteratorBucket = EndOfList) and (iteratorSlot < maxIndex) do
begin
iteratorSlot := iteratorSlot + 1;
{$PUSH}
{$R-}
iteratorBucket := slots[iteratorSlot];
{$POP}
end;
end;
end; {InternalAdvanceIterator}
function GetNextHashEntry (theTable: HashTable; var keyOffset: Longint; var keyLength: Integer; var valueOffset: Longint; var valueLength: Integer; var dataBlock: Handle; var state: Longint): OSErr;
var
bucketPtr: TableEntryPtr;
begin
with TableHandle(theTable)^^ do
if (state <> 0) and (bucketCount <> iteratorCheckCount) then
begin
GetNextHashEntry := paramErr;
state := 0;
Exit(GetNextHashEntry);
end;
InternalAdvanceIterator(TableHandle(theTable), state = 0);